﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Threading;
using Kyoh.App.Crawler.Plugin;
using Kyoh.Lib.Component;
using Kyoh.Lib.Component.Globalization;
using Kyoh.Lib.IO;

namespace Kyoh.App.Crawler.Contents
{
    [Serializable]
    sealed class Loader : ISeed, ILoader, ISerializable, IDisposable
    {
        public Loader()
        {
            Data = new StringDictionary();
            Debug.WriteLine("Loader.ctor()");
        }

        public Loader(ILoader loader)
        {
            Refresh(loader);
            Debug.WriteLine("Loader.ctor(ILoader)");
        }

        public Loader(Loader loader)
        {
            Refresh(loader);
            Debug.WriteLine("Loader.ctor(Loader)");
        }

        internal void Refresh(ILoader loader)
        {
            CancelDownload();
            if (loader == null)
                return;
            Title = loader.Title;
            Uri = loader.Uri;
            ContentUri = loader.ContentUri;
            Thumbnail = new Bitmap(loader.Thumbnail);
            FileName = PathEx.ReplaceInvalidFileNameChars(loader.FileName);
            Summary = loader.Summary;
            Data = new StringDictionary();
            Request = loader.Request;
            foreach (string key in loader.Data.Keys)
                Data.Add(key, loader.Data[key]);
        }

        internal void Refresh(Loader loader)
        {
            Refresh((ILoader)loader);

            BytesReceived = 0L;
            TotalBytesToReceive = 0L;
            ProgressPercentage = 0;
            lock (_locker)
            {
                Called = false;
                Completed = false;
            }
            Cancelled = false;
            if (loader != null)
                Priority = loader._priority;
            Error = null;
        }

        #region ダウンロード
        private WebClient _client;
        private Uri _contentUriObject;

        private readonly object _locker = new object();

        public void Download(string donwloadDirectoryPath)
        {
            try
            {
                Debug.Write(GetHashCode() + ": Download");
                Debug.WriteLine(string.Format("({0}, {1}", Run, _completed));
                lock (_locker)
                {
                    if (Called)
                        return;
                    Called = true;
                    Completed = false;
                }
                Debug.Write(GetHashCode() + ": Download");
                Debug.WriteLine(string.Format("({0}, {1}", Run, _completed));
                using (var waitHandle = new ManualResetEvent(false))
                {
                    using (_client = new WebClient())
                    {
                        _client.DownloadProgressChanged += DownloadProgressChanged;
                        _client.DownloadFileCompleted += DownloadFileCompleted;
                        //HACK:parserでILoader.Dataに「"HttpRefer"」を追加した場合、そっちを使う
                        _client.Headers.Add(HttpRequestHeader.Referer, Uri);
#if DEBUG_
                        client.DownloadFileAsync(new Uri(@"http://kyoh.net/license.txt"), GetDownloadFullPath(donwloadDirectoryPath), waitHandle);
#else
                        _client.DownloadFileAsync(_contentUriObject, GetDownloadFullPath(donwloadDirectoryPath), waitHandle);
#endif
                        waitHandle.WaitOne();
                    }
                }
            }
            catch (Exception exc)
            {
                Debug.WriteLine(exc);
                Trace.TraceError(Properties.Strings.ErrorDownload, UniqueKey);
                _error = exc;
            }
        }

        private class CookieWebClient : WebClient
        {
            WebRequest _request = null;
            public CookieWebClient(WebRequest request)
            {
                _request = request;
            }

            protected override WebRequest GetWebRequest(Uri address)
            {
                return _request ?? base.GetWebRequest(address);
            }
        }

        private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            BytesReceived = e.BytesReceived;
            TotalBytesToReceive = e.TotalBytesToReceive;
            ProgressPercentage = e.ProgressPercentage;
            OnDownloadProgress(e);
        }

        private void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            lock (_locker)
                Completed = true;

            _client = null;
            Cancelled = e.Cancelled;
            Error = e.Error;
            ((ManualResetEvent)e.UserState).Set();
            OnDonloadComplete(e);
        }

        /// <summary>ダウンロードの完了時に発生します。</summary>
        public event AsyncCompletedEventHandler DownloadCompleted;

        private void OnDonloadComplete(AsyncCompletedEventArgs e)
        {
            if (DownloadCompleted != null)
                DownloadCompleted(this, e);
        }
        /// <summary>データの一部またはすべてが正常にダウンロードされた場合に発生します。</summary>
        public event DownloadProgressChangedEventHandler DownloadProgress;

        private void OnDownloadProgress(DownloadProgressChangedEventArgs e)
        {
            if (DownloadProgress != null)
                DownloadProgress(this, e);
        }

        public void CancelDownload()
        {
            try
            {
                if (_client != null)
                    _client.CancelAsync();
            }
            catch(Exception exc)
            {
                Trace.TraceError(Properties.Strings.ErrorCancelDownload, UniqueKey);
                Debug.WriteLine(exc);
            }
        }

        private string GetDownloadFullPath(string downloadDirectoryPath)
        {
            if (string.IsNullOrWhiteSpace(_fileName))
                FileName = Path.GetRandomFileName();
            foreach (var c in Path.GetInvalidFileNameChars())
                FileName = FileName.Replace(c, '_');

            for (ulong i = 1; i < ulong.MaxValue; i++)
            {
                try
                {
                    FullPath = BuildFullPath(downloadDirectoryPath, FileName, i);
                    using (new FileStream(FullPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
                        return FullPath;
                }
                catch (IOException exc)
                {
                    Debug.WriteLine(exc);
                }
            }
            throw new NotSupportedException();
        }

        private static string BuildFullPath(string directoryPath, string fileName, ulong duplicateIndex)
        {
            if (duplicateIndex < 2)
                return Path.Combine(directoryPath, fileName);

            var ext = Path.GetExtension(fileName);
            return Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(fileName) + string.Format("({0})", duplicateIndex) + ext);
        }

        #endregion

        #region ダウンロード状況

        private string _fullPath;
        private long _bytesReceived;
        private long _totalBytesToReceive;
        private int _progressPercentage;
        private bool _completed;
        private bool _cancelled;
        private SeedPriority _priority;
        private Exception _error;

        /// <summary>ダウンロードしたフルパスを取得します。</summary>
        [Browsable(false)]
        public string FullPath
        {
            get { return _fullPath; }
            set
            {
                if (_fullPath == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameFullPath);
                _fullPath = value;
                OnPropertyChanged(LoaderProperties.PropertyNameFullPath);
            }
        }

        /// <summary>ダウンロードしたバイト数を取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameBytesReceived)]
        [PropertyIndex(4)]
        [DisplayAutoSize(DisplayAutoSizeMode.NameAndValues)]
        [Editable(false)]
        public long BytesReceived
        {
            get { return _bytesReceived; }
            set
            {
                if (_bytesReceived == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameBytesReceived);
                _bytesReceived = value;
                OnPropertyChanged(LoaderProperties.PropertyNameBytesReceived);
            }
        }
        /// <summary>データのダウンロード操作の総バイト数を取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameTotalBytesToReceive)]
        [PropertyIndex(5)]
        [DisplayAutoSize(DisplayAutoSizeMode.NameAndValues)]
        [Editable(false)]
        public long TotalBytesToReceive
        {
            get { return _totalBytesToReceive; }
            set
            {
                if (_totalBytesToReceive == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameTotalBytesToReceive);
                _totalBytesToReceive = value;
                OnPropertyChanged(LoaderProperties.PropertyNameTotalBytesToReceive);
            }
        }
        /// <summary>ダウンロードの進行状況の割合を取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameProgressPercentage)]
        [PropertyIndex(3)]
        [DisplayAutoSize(DisplayAutoSizeMode.None)]
        [DisplaySize(100)]
        [Editable(false)]
        public int ProgressPercentage
        {
            get { return _progressPercentage; }
            set
            {
                if (_progressPercentage == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameProgressPercentage);
                _progressPercentage = value;
                OnPropertyChanged(LoaderProperties.PropertyNameProgressPercentage);
            }
        }

        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameStatusImage)]
        [PropertyIndex(0)]
        [DisplayAutoSize(DisplayAutoSizeMode.NameAndValues)]
        public Image StatusImage
        {
            get
            {
                if (!Called)
                    return Properties.Resources.circle_white;
                if (Cancelled)
                    return Properties.Resources.circle_yellow;
                if (Error != null)
                    return Properties.Resources.circle_red;
                if (Completed)
                    return Properties.Resources.accept;
                return Properties.Resources.circle_green;
            }
        }

        [Browsable(false)]
        public bool Called
        {
            get { return Run; }
            private set
            {
                if (Run == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameCalled);
                OnPropertyChanging(LoaderProperties.PropertyNameStatusImage);
                Run = value;
                OnPropertyChanged(LoaderProperties.PropertyNameCalled);
                OnPropertyChanged(LoaderProperties.PropertyNameStatusImage);
            }
        }
        [Browsable(false)]
        public bool Completed
        {
            get { return _completed; }
            private set
            {
                if (_completed == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameCompleted);
                OnPropertyChanging(LoaderProperties.PropertyNameStatusImage);
                _completed = value;
                OnPropertyChanged(LoaderProperties.PropertyNameCompleted);
                OnPropertyChanged(LoaderProperties.PropertyNameStatusImage);
            }
        }

        /// <summary>ダウンロードがキャンセルされたかどうかを示す値を取得します。</summary>
        [Browsable(false)]
        public bool Cancelled
        {
            get { return _cancelled; }
            set
            {
                if (_cancelled == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameCancelled);
                OnPropertyChanging(LoaderProperties.PropertyNameStatusImage);
                _cancelled = value;
                OnPropertyChanged(LoaderProperties.PropertyNameCancelled);
                OnPropertyChanged(LoaderProperties.PropertyNameStatusImage);
            }
        }
        [PropertyIndex(2)]
        [DisplayAutoSize(DisplayAutoSizeMode.NameAndValues)]
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNamePriority)]
        [EnumDataType(typeof(SeedPriority))]
        public SeedPriority Priority
        {
            get { return _priority; }
            set
            {
                if (_priority == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNamePriority);
                _priority = value;
                OnPropertyChanged(LoaderProperties.PropertyNamePriority);
            }
        }
        /// <summary>ダウンロード中に発生したエラーを示す値を取得します。</summary>
        [Browsable(false)]
        public Exception Error
        {
            get { return _error; }
            set
            {
                if (_error == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameError);
                OnPropertyChanging(LoaderProperties.PropertyNameStatusImage);
                _error = value;
                OnPropertyChanged(LoaderProperties.PropertyNameError);
                OnPropertyChanged(LoaderProperties.PropertyNameStatusImage);
            }
        }
        #endregion

        #region ILoader メンバー

        private string _title;
        [PropertyIndex(1)]
        [DisplayAutoSize(DisplayAutoSizeMode.NameAndValues)]
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameTitle)]
        [Editable(false)]
        public string Title
        {
            get { return _title; }
            private set
            {
                if (_title == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameTitle);
                _title = value;
                OnPropertyChanged(LoaderProperties.PropertyNameTitle);
            }
        }
        private string _pageUri;
        [PropertyIndex(6)]
        [DisplayAutoSize(DisplayAutoSizeMode.Values)]
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameUri)]
        [Editable(false)]
        public string Uri
        {
            get { return _pageUri; }
            set
            {
                if (_pageUri == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameUri);
                _pageUri = value;
                OnPropertyChanged(LoaderProperties.PropertyNameUri);
            }
        }

        [PropertyIndex(7)]
        [DisplayAutoSize(DisplayAutoSizeMode.Values)]
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameContentUri)]
        [Editable(false)]
        public string ContentUri
        {
            get { return UniqueKey; }
            private set
            {
                if (UniqueKey == value)
                    return;
                _contentUriObject = new Uri(value);
                OnPropertyChanging(LoaderProperties.PropertyNameContentUri);
                UniqueKey = value;
                OnPropertyChanged(LoaderProperties.PropertyNameContentUri);
            }
        }

        [PropertyIndex(8)]
        [DisplayAutoSize(DisplayAutoSizeMode.None)]
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameThumbnail)]
        [Editable(false)]
        public Image Thumbnail { get; private set; }

        [PropertyIndex(9)]
        [DisplayAutoSize(DisplayAutoSizeMode.None)]
        [DisplayNameFromResource(typeof(Properties.DisplayName), LoaderProperties.PropertyNameSummary)]
        [Editable(false)]
        public string Summary { get; private set; }

        private string _fileName;
        [Browsable(false)]
        public string FileName
        {
            get { return _fileName; }
            private set
            {
                if (value != null)
                    value = value.TrimStart('.');
                if (_fileName == value)
                    return;
                OnPropertyChanging(LoaderProperties.PropertyNameFileName);
                _fileName = value;
                OnPropertyChanged(LoaderProperties.PropertyNameFileName);
            }
        }
        [Browsable(false)]
        public WebRequest Request { get; private set; }
        [Browsable(false)]
        public StringDictionary Data { get; private set; }
        #endregion

        #region ISerializable メンバー

        private Loader(SerializationInfo info, StreamingContext context)
        {
            Uri = info.GetString(LoaderProperties.PropertyNameUri);
            _title = info.GetString(LoaderProperties.PropertyNameTitle);
            var thumbs = info.GetString(LoaderProperties.PropertyNameThumbnail);
            if (!string.IsNullOrWhiteSpace(thumbs))
                using (var stream = new MemoryStream(Convert.FromBase64String(thumbs)))
                using (var image = Image.FromStream(stream))
                    Thumbnail = new Bitmap(image);
            else
                Thumbnail = null;
            ContentUri = info.GetString(LoaderProperties.PropertyNameContentUri);
            _fileName = info.GetString(LoaderProperties.PropertyNameFileName);
            Summary = info.GetString(LoaderProperties.PropertyNameSummary);
            _fullPath = info.GetString(LoaderProperties.PropertyNameFullPath);
            _bytesReceived = info.GetInt64(LoaderProperties.PropertyNameBytesReceived);
            _totalBytesToReceive = info.GetInt64(LoaderProperties.PropertyNameTotalBytesToReceive);
            _progressPercentage = info.GetInt32(LoaderProperties.PropertyNameProgressPercentage);
            Run = info.GetBoolean(LoaderProperties.PropertyNameCalled);
            _completed = info.GetBoolean(LoaderProperties.PropertyNameCompleted);
            _cancelled = info.GetBoolean(LoaderProperties.PropertyNameCancelled);
            _priority = (SeedPriority)info.GetValue(LoaderProperties.PropertyNamePriority, typeof(SeedPriority));
            _error = (Exception)info.GetValue(LoaderProperties.PropertyNameError, typeof(Exception));
            Data = (StringDictionary)info.GetValue(LoaderProperties.PropertyNameData, typeof(StringDictionary));
            if (Run && !_completed && _error == null)
                Run = false;
        }
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue(LoaderProperties.PropertyNameUri, Uri);
            info.AddValue(LoaderProperties.PropertyNameTitle, _title);
            if (Thumbnail == null)
                info.AddValue(LoaderProperties.PropertyNameThumbnail, string.Empty);
            else
            {
                using (var stream = new MemoryStream())
                {
                    Thumbnail.Save(stream, ImageFormat.Bmp);
                    info.AddValue(LoaderProperties.PropertyNameThumbnail, Convert.ToBase64String(stream.GetBuffer()));
                }
            }
            info.AddValue(LoaderProperties.PropertyNameContentUri, UniqueKey);
            info.AddValue(LoaderProperties.PropertyNameFileName, _fileName);
            info.AddValue(LoaderProperties.PropertyNameSummary, Summary);
            info.AddValue(LoaderProperties.PropertyNameFullPath, _fullPath);
            info.AddValue(LoaderProperties.PropertyNameBytesReceived, _bytesReceived);
            info.AddValue(LoaderProperties.PropertyNameTotalBytesToReceive, _totalBytesToReceive);
            info.AddValue(LoaderProperties.PropertyNameProgressPercentage, _progressPercentage);
            info.AddValue(LoaderProperties.PropertyNameCalled, Run);
            info.AddValue(LoaderProperties.PropertyNameCompleted, _completed);
            info.AddValue(LoaderProperties.PropertyNameCancelled, _cancelled);
            info.AddValue(LoaderProperties.PropertyNamePriority, _priority);
            info.AddValue(LoaderProperties.PropertyNameError, _error);
            info.AddValue(LoaderProperties.PropertyNameData, Data);
        }
        #endregion

        #region INotifyPropertyChanged, INotifyPropertyChanging メンバー

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, e);
        }

        public event PropertyChangingEventHandler PropertyChanging;

        private void OnPropertyChanging(string propertyName)
        {
            OnPropertyChanging(new PropertyChangingEventArgs(propertyName));
        }

        private void OnPropertyChanging(PropertyChangingEventArgs e)
        {
            var handler = PropertyChanging;
            if (handler != null)
                handler(this, e);
        }

        #endregion

        #region IDisposable メンバー

        public void Dispose()
        {
            CancelDownload();
        }

        ~Loader()
        {
            CancelDownload();
        }

        #endregion

        #region ISeed メンバー

        [Browsable(false)]
        public string UniqueKey { get; private set; }

        [Browsable(false)]
        public bool Run { get; private set; }

        #endregion
    }
    public static class LoaderProperties
    {
        public const string PropertyNameTitle = "Title";
        public const string PropertyNameThumbnail = "Thumbnail";
        public const string PropertyNameUri = "Uri";
        public const string PropertyNameContentUri = "ContentUri";
        public const string PropertyNameFileName = "FileName";
        public const string PropertyNameSummary = "Summary";
        public const string PropertyNameData = "Data";
        public const string PropertyNameBytesReceived = "BytesReceived";
        public const string PropertyNameTotalBytesToReceive = "TotalBytesToReceive";
        public const string PropertyNameProgressPercentage = "ProgressPercentage";
        public const string PropertyNameCalled = "Called";
        public const string PropertyNameCompleted = "Completed";
        public const string PropertyNameCancelled = "Cancelled";
        public const string PropertyNameError = "Error";
        public const string PropertyNamePriority = "Priority";
        public const string PropertyNameStatusImage = "StatusImage";
        public const string PropertyNameFullPath = "FullPath";

    }
}